﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Microsoft.Practices.ServiceLocation;

namespace SimpleContainer
{
    /// <summary>
    /// Very lightweight IoC container.
    /// </summary>
    public class SimpleContainer : ServiceLocatorImplBase, IDisposable
    {

        // type -> key -> obtainer
        protected readonly Dictionary<Type, Dictionary<string, Func<SimpleContainer, Type, string, object>>> _TypeToCreators = new Dictionary<Type, Dictionary<string, Func<SimpleContainer, Type, string, object>>>();

        public IServiceLocator ParentContainer
        {
            get; set;
        }
        
        protected override object DoGetInstance(Type serviceType, string key)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            var creators = _TypeToCreators[serviceType];
            if (creators != null && creators.Count > 0)
            {
                Func<SimpleContainer, Type, string, object> creator;
                if (string.IsNullOrEmpty(key) || !creators.TryGetValue(key, out creator) || creator == null)
                {
                    // fallback to empty key
                    creators.TryGetValue(string.Empty, out creator);
                }
                if (creator != null)
                {
                    return creator(this, serviceType, key);
                }
            }
            // try to use parent container before resolving fail
            if (ParentContainer != null)
            {
                return ParentContainer.GetInstance(serviceType, key);
            }
            throw new ActivationException(string.Format("Service '{0}' with key '{1}' not found.", serviceType.AssemblyQualifiedName, key));
        }


        protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            var creators = _TypeToCreators[serviceType];
            if (creators == null || creators.Count == 0)
            {
                yield break;
            }
            foreach (var pair in creators)
            {
                yield return pair.Value(this, serviceType, pair.Key);
            }
            if (ParentContainer != null)
            {
                foreach (var instance in ParentContainer.GetAllInstances(serviceType))
                {
                    yield return instance;
                }
            }
        }
        


        public void Register(Type type, string key, Func<SimpleContainer, Type, string, object> creator)
        {
            if (creator == null)
            {
                throw new ArgumentNullException("creator");
            }
            Dictionary<string, Func<SimpleContainer, Type, string, object>> creators;
            if (!_TypeToCreators.TryGetValue(type, out creators))
            {
                _TypeToCreators.Add(type, creators = new Dictionary<string, Func<SimpleContainer, Type, string, object>>(StringComparer.OrdinalIgnoreCase));
            }
            creators[key ?? string.Empty] = creator;
        }



        #region disposing

        public event EventHandler Disposing;
        protected void OnDisposing(EventArgs ea)
        {
            var eh = Disposing;
            if (eh != null)
            {
                eh(this, ea);
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            OnDisposing(EventArgs.Empty);
        }

        protected bool _Disposed; 
        public void Dispose()
        {
            if (!_Disposed)
            {
                try
                {
                    Dispose(true);
                }
                finally
                {
                    _Disposed = true;
                    GC.SuppressFinalize(this);
                }
            }
        }

        ~SimpleContainer()
        {
            Dispose(false);
        }

        #endregion

    }
}
